#!/usr/bin/env python
# encoding: utf-8

import serial

import threading
import binascii
import struct
import collections


SERIAL_LOG_INFO = 0x01
SERIAL_LOG_NOTICE = 0x02
SERIAL_LOG_WARNING = 0x04
SERIAL_LOG_ERR = 0x08
SERIAL_LOG_DEBUG = 0x10

class Ser:
    """串口接收消息类,调用pyserial"""
    def __init__(self):
        # serial args
        self.serial = serial.Serial()
        self.serial.timeout  = 0.1
        self.serial.port     = "COM1"
        self.serial.baudrate = 9600
        self.serial.bytesize = 8
        self.serial.parity   = "N"
        self.serial.stopbits = 1
        self.max_recv_buf_len = 255

        self._out_packet = collections.deque()
        # 串口接收线程
        self._thread = None
        self.serial_alive = False


        self._on_message = None
        self._on_send = None
        self._on_log = None
        self._logger = None
        # 消息接收线程锁
        self._callback_mutex = threading.RLock()

    def open_serial(self):
        if self.serial.port and self.serial.baudrate:
            try:
                self.serial.open()
            except serial.SerialException as e:
                self._easy_log(SERIAL_LOG_ERR, "open serial error!!! %s", e)
            else:
                self.serial_alive = True
                self._thread = threading.Thread(target=self._recv)
                self._thread.setDaemon(True)
                self._thread.start()
                self._easy_log(SERIAL_LOG_INFO, "open serial success: %s / %s",self.serial.port, self.serial.baudrate)
        else:
            self._easy_log(SERIAL_LOG_ERR, "port is not setting!!!")

    def _recv(self):
        """serial recv thread"""
        while self.serial_alive:
            while self.serial_alive:
                try:
                    b = self.serial.read(self.max_recv_buf_len)
                    if not b:
                        break
                    # bytes转为16进制字符串
                    s = str(binascii.b2a_hex(b).decode('utf-8')).upper()
                    # byte, = struct.unpack("!B", byte)
                    # 线程中用句柄接收串口消息
                    self._handle_on_message(s)
                    self._easy_log(SERIAL_LOG_INFO, "serial receive message:%s", b)
                except Exception as e:
                    self.serial_alive = False
                    self._easy_log(SERIAL_LOG_ERR, "serial err:%s", e)

    @property
    def on_message(self):
        """ 回调函数 on_message
            当串口接收消息时调用_handle_on_message会执行此函数
            回调函数在接收到串口消息时执行

        """
        return self._on_message

    @on_message.setter
    def on_message(self, func):
        with self._callback_mutex:
            self._on_message = func

    def _handle_on_message(self, message):
        """串口接收消息,handle"""
        self.on_message(message)

    def on_send(self, msg):
        """串口写入msg,写入需要将字符串转换成bytes"""
        with self._callback_mutex:
            if msg:
                if isinstance(msg, bytes):
                    self.serial.write(msg)
                if isinstance(msg, str):
                    self.serial.write(msg.encode('utf-8'))
            self._easy_log(SERIAL_LOG_INFO, "serial send message: %s", msg)

    @property
    def on_log(self):
        """If implemented, called when the serial has log information.
        Defined to allow debugging."""
        return self._on_log

    @on_log.setter
    def on_log(self, func):
        """ Define the logging callback implementation.

        Expected signature is:
            log_callback(level, buf)

        level:      gives the severity of the message and will be one of
                    SERIAL_LOG_INFO, SERIAL_LOG_NOTICE, SERIAL_LOG_WARNING,
                    SERIAL_LOG_ERR, and SERIAL_LOG_DEBUG.
        buf:        the message itself
        """
        self._on_log = func

    def _easy_log(self, level, fmt, *args):
        if self.on_log is not None:
            buf = fmt % args
            try:
                self.on_log(level, buf)
            except Exception:
                # Can't _easy_log this, as we'll recurse until we break
                pass # self._logger will pick this up, so we're fine

class Serial:
    def __init__(self, app):
        self.ser = Ser()
        if app is not None:
            self.init_app(app)
        else:
            self.ser.open_serial()

    def init_app(self, app):
        self.serial.timeout  = app.config.get("SERIAL_TIMEOUT")
        self.serial.port     = app.config.get("SERIAL_PORT")
        self.serial.baudrate = app.config.get("SERIAL_BAUDRATE")
        self.serial.bytesize = app.config.get("SERIAL_BYTESIZE")
        self.serial.parity   = app.config.get("SERIAL_PARITY")
        self.serial.stopbits = app.config.get("SERIAL_STOPBITS")

        self.serial_alive = False
        self.ser.open_serial()

    def on_message(self):
        """绑定串口接收消息到此装饰器中
        用法：
            @serial.on_message()
            def handle_message(msg):
                print("接收到串口消息：", msg)
        """
        def decorator(handler):
            # type: (Callable) -> Callable
            self.ser.on_message = handler
            return handler
        return decorator

    def on_send(self, msg):
        """绑定串口发送消息
        用法：
            serial.on_send("发送一条串口消息")
        """
        self.ser.on_send(msg)

    def on_log(self):
        def decorator(handler):
            # type: (Callable) -> Callable
            self.ser.on_log = handler
            return handler
        return decorator


s = Serial(None)

@s.on_message()
def handler_message(msg):
    s.on_send("sdfsdf")

@s.on_log()
def handle_logging(level, buf):
    print(buf)

while True:
    pass
